use anyhow::{Ok, Result};

use std::collections::HashMap;
use std::io::IsTerminal;
use std::io::Read;
use std::path::Path;

use crate::cli;
use crate::error::ZipNoteError;
use crate::utils;
use crate::zip;

const ZIP_COMMENT: &str = "ZIP_COMMENT";

pub fn main() -> Result<()> {
    // 初始化日志
    utils::common::init_logger();

    // 解析命令行参数
    let args = cli::parse_args_note();

    log::debug!("Parsed arguments: {:?}", args);

    // 打印相关的帮助信息
    {
        if args.version {
            cli::show_version_note();
            return Ok(());
        }
        if args.license {
            cli::show_license_note();
            return Ok(());
        }
    }

    // 获取要处理的ZIP文件路径
    if args.zipfile.is_none() {
        log::error!("Zip file not specified!");
        return Err(ZipNoteError::InvalidArguments("need to specify zip file".to_string()).into());
    }
    let zip_path = args.zipfile.clone().unwrap();
    // 检查ZIP文件是否存在
    if !zip_path.exists() {
        log::error!("Zip file not found: {}", zip_path.display());
        return Err(ZipNoteError::ArchiveNotFound(zip_path.display().to_string()).into());
    }

    if args.write {
        write_comments(&args)?;
        return Ok(());
    }

    list_comments(&zip_path)?;

    Ok(())
}

fn list_comments(zip_path: &std::path::Path) -> Result<()> {
    // 打开ZIP文件
    let zip_archive = zip::ZipArchive::new(zip_path.to_str().unwrap())?;

    let header_counts = zip_archive.len();
    // 遍历ZIP文件中的每个条目
    for i in 0..header_counts {
        let file = zip_archive.by_index_raw(i)?;
        // 读取ZIP文件条目的注释
        let comment = file.comments();
        println!("@ {}", file.name());
        // 如果条目有注释，则打印出来
        if !comment.is_empty() {
            println!("{}", comment);
        }
        println!("@ (comment above this line)");
    }
    println!("@ (zip file comment below this line)");
    let archive_comment = zip_archive.comment();
    if !archive_comment.is_empty() {
        println!("{}", archive_comment);
    }

    Ok(())
}

fn write_comments(args: &cli::ZipNoteArgs) -> Result<()> {
    let all_input = read_input_stdin()?;

    log::debug!("all_input: {:?}", all_input);
    let comment_map = construct_comment_map(&all_input)?;
    log::debug!("comment_map: {:?}", comment_map);
    if comment_map.is_empty() {
        log::warn!("comment_map is empty");
        return Err(ZipNoteError::InvalidCommentFormat("unexpected input".to_string()).into());
    }

    // 打开ZIP文件
    let zip_path = args.zipfile.clone().unwrap();
    let zip_archive = zip::ZipArchive::new(zip_path.to_str().unwrap())?;
    // 创建临时文件 - 优先使用args.temp_path，否则使用zip文件所在目录
    let temp_dir = if let Some(temp_path) = &args.temp_path {
        temp_path.as_path()
    } else {
        zip_path.parent().unwrap_or_else(|| Path::new("."))
    };
    let temp_file = tempfile::NamedTempFile::new_in(temp_dir)?;
    let mut zip_writer = zip::ZipWriter::new(temp_file.path().to_str().unwrap())?;

    // 校验comment_map中的文件名是否都存在于ZIP文件中
    validate_input_entry(zip_archive.file_names(), comment_map.keys())?;

    let header_counts = zip_archive.len();
    // 遍历ZIP文件中的每个条目
    for i in 0..header_counts {
        let mut file = zip_archive.by_index_raw(i)?;
        let file_name = file.name();

        let new_comments = if let Some(comment) = comment_map.get(&file_name) {
            log::debug!(
                "file {} found in comment map, new_comments: {}",
                file_name,
                comment
            );
            comment.clone()
        } else {
            file.comments()
        };

        file.set_comments(&new_comments);

        zip_writer.raw_copy_file(&file, file_name)?;
    }

    // 处理ZIP文件的注释
    let zip_comment = comment_map.get(ZIP_COMMENT);
    if zip_comment.is_some() {
        let zip_comment = zip_comment.unwrap();
        log::debug!("zip_comment: {}", zip_comment);
        zip_writer.set_archive_comment(zip_comment);
    }
    // 关闭ZIP文件
    zip_writer.finish()?;

    // 替换原始ZIP文件
    temp_file.persist(zip_path)?;

    Ok(())
}

// 从stdin读取内容的函数
fn read_input_stdin() -> Result<Vec<String>> {
    let mut all_comments = Vec::new();
    let mut stdin = std::io::stdin();
    let mut buffer = String::new();

    if stdin.is_terminal() {
        log::debug!("stdin is terminal");
        let mut line = String::new();
        // 读取第一行并校验格式
        stdin.read_line(&mut line)?;
        if !line.starts_with("@ ") {
            return Err(ZipNoteError::InvalidCommentFormat(
                "first line must start with '@ filename'".to_string(),
            )
            .into());
        }
        all_comments.push(line.trim_end().to_string());
        // 继续读取剩余行，直到EOF
        loop {
            line.clear();
            let bytes_read = stdin.read_line(&mut line)?;
            if bytes_read == 0 {
                // 检测到EOF (Ctrl+D)
                break;
            }
            // 保留原始换行符
            all_comments.push(line.to_string());
        }
    } else {
        stdin.read_to_string(&mut buffer)?;
        // 按行分割但保留原始格式
        all_comments = buffer.lines().map(|s| s.to_string()).collect();
    }

    Ok(all_comments)
}

// 将获取的注释，构造为map，key为文件名，value为注释内容
fn construct_comment_map(comments: &[String]) -> Result<HashMap<String, String>> {
    let mut comment_map = HashMap::new();
    let mut current_file = String::new();
    let mut current_comment = String::new();
    let mut zip_comment = String::new();
    let mut is_zip_comment = false;

    for line in comments {
        match line.as_str() {
            "@ (comment above this line)" => continue,
            "@ (zip file comment below this line)" => {
                is_zip_comment = true;
            }
            s if s.starts_with("@ ") => {
                // 校验格式并提取文件名
                let file_name = s.trim_start_matches("@ ").to_string();
                if file_name.is_empty() {
                    return Err(ZipNoteError::InvalidArguments(
                        "invalid format: @ must be followed by filename".to_string(),
                    )
                    .into());
                }

                // 处理前一个文件的注释
                if !current_file.is_empty() {
                    comment_map.insert(
                        std::mem::take(&mut current_file),
                        std::mem::take(&mut current_comment),
                    );
                }
                current_file = file_name;
            }
            s if s.starts_with("@=") => {
                return Err(ZipNoteError::InvalidArguments(
                    "renaming entries is not supported".to_string(),
                )
                .into());
            }
            s if !s.is_empty() => {
                // 处理转义字符
                let processed_line = s.replace("\\@", "@").replace("\\\\", "\\");

                if is_zip_comment {
                    zip_comment.push_str(&processed_line);
                    zip_comment.push('\n');
                } else if current_file.is_empty() {
                    return Err(ZipNoteError::InvalidArguments(
                        "comment line appears before @ filename marker".to_string(),
                    )
                    .into());
                } else {
                    current_comment.push_str(&processed_line);
                    // current_comment.push('\n');
                }
            }
            _ => {}
        }
    }

    if !current_file.is_empty() {
        comment_map.insert(current_file, current_comment.trim().to_string());
    }

    if !zip_comment.is_empty() {
        comment_map.insert(ZIP_COMMENT.to_string(), zip_comment.trim().to_string());
    }

    Ok(comment_map)
}

// 验证输入的entry是否存在于zip文件中
fn validate_input_entry<'a>(
    arhive_entry_names: Vec<String>,
    entry_name_keys: impl Iterator<Item = &'a String>,
) -> Result<()> {
    for entry in entry_name_keys {
        if entry == ZIP_COMMENT {
            continue;
        }
        if !arhive_entry_names.contains(entry) {
            return Err(ZipNoteError::InvalidCommentFormat(format!(
                "following entries not found in zip file: {}",
                entry
            ))
            .into());
        }
    }

    Ok(())
}
